import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Date;

/**
 * QUIRK.java
 * 
 * This is the main class that runs the program. It is invoked with no
 * command line arguments.
 * 
 * @author Roma Kane, Zachary M. Allen
 */
public class QUIRK {

	/**
	 * Name of the index file
	 */
	private static final String INDEX = "AIM.out";

	/**
	 * Constant for an inappropriate request
	 */
	private static final String INAPPROPRIATE_REQUEST = "Inappropriate Request";

	/**
	 * Stores the last valid index so that a false positive still allows the
	 * SHOW command to work
	 */
	private static int LAST_VALID_INDEX = 0;

	/**
	 * Count of the number of times the user has run the STATS command
	 */
	private static int STATS_COUNT = 0;

	/**
	 * Count of the number of times the user has run the INDEX command
	 */
	private static int INDEX_COUNT = 0;

	/**
	 * Count of the number of times the user has run the QUERY command
	 */
	private static int QUERY_COUNT = 0;

	/**
	 * Count of the number of times the user has run the SHOW command
	 */
	private static int SHOW_COUNT = 0;

	/**
	 * Count of the number of times the user has run the NEXT_COUNT command
	 */
	private static int NEXT_COUNT = 0;

	/**
	 * Count of the number of times the user has run the REFINE command
	 */
	private static int REFINE_COUNT = 0;

	/**
	 * Count of the number of times the user has run the QUIT command
	 */
	private static int QUIT_COUNT = 0;

	/**
	 * Results of the most recent query
	 */
	private static ResultSet resultSet;

	/**
	 * The reader that pulls user input from the standard input stream
	 */
	private static BufferedReader in;

	/**
	 * The user's most recent command
	 */
	private static String input;

	/**
	 * The total number of false positives returned so far
	 */
	private static int totalFalsePositives = 0;

	/**
	 * The total number of results returned so far
	 */
	private static int totalResults = 0;

	/**
	 * True if a result set has been generated, false otherwise
	 */
	private static boolean resultSetGenerated;

	/**
	 * The position of the current instance in the result set
	 */
	private static int positionForShowNext;

	/**
	 * Displays the prompt to the user and reads a line of input from the
	 * standard input stream
	 * @throws    Throwable
	 */
	private static void prompt() throws Throwable {
		System.out.print("\n??? ");
		input = in.readLine().trim().toLowerCase();
	}

	/**
	 * Prints statistics for the index when the user enters the STATS command
	 * @param    fileIndex    The index object to read from
	 */
	private static void printStats(FileIndex fileIndex) {
		System.out.println("Corpus Files:");
		for (int i = 0; i < fileIndex.getCorpusTotalFiles(); i++) {
			FileIndexInstance currFile = (FileIndexInstance) fileIndex
					.getFileIndexInstances().get(i);
			String temp = currFile.filename;
			while (temp.indexOf(File.separator) > -1) {
				temp = temp.substring(temp.indexOf(File.separator) + 1);
			}
			System.out.println("File name: " + temp);
			System.out.println(" * " + currFile.wordSize + " words, "
					+ currFile.charSize + " characters, " + currFile.lineSize
					+ " lines");
		}
		System.out.println("Corpus Summary:");
		System.out.println(" " + fileIndex.getCorpusTotalChars()
				+ " characters");
		System.out.println(" " + fileIndex.getCorpusTotalWords() + " words");
		System.out.println(" " + fileIndex.getCorpusTotalLines() + " lines");
		System.out.println(" " + fileIndex.getCorpusTotalFiles() + " files");
	}

	/**
	 * Prints information about the index when the user enters the INDEX
	 * command
	 * @param    fileIndex    The index object to read from
	 */
	private static void printIndex(FileIndex fileIndex) {
		System.out.println("Name: " + INDEX);
		System.out.println("Date and time: "
				+ new Date(fileIndex.getTime().getTimeInMillis()).toString());
		System.out.println("Break Characters excluding quotes: \""
				+ fileIndex.getVariableBreaks() + "\"");
		if (fileIndex.getStopWordFile() == null) {
			System.out.println("No stop word file was used");
		} else {
			System.out
					.println("Stop word file: " + fileIndex.getStopWordFile());
		}
	}

	/**
	 * Handles a user query (whether new or a refine)
	 * @param    corpusIndexReader    The index reader used to read the index
	 * @param    fileIndex    The index object used
	 * @param    rawQuery    The query to process
	 * @throws    QUIRKException
	 */
	private static void handleQueryCase(CorpusIndexReader corpusIndexReader,
			FileIndex fileIndex, String rawQuery) throws QUIRKException {

		QueryProcessFacade qaf = QueryProcessFacade.Instance();
		qaf.reset();
		qaf = QueryProcessFacade.Instance();
		Query q = new Query(rawQuery);
		qaf.processQuery(q, fileIndex, corpusIndexReader);
		resultSet = qaf.getMostRecentResult();
		totalFalsePositives += resultSet.getNumberOfFalsePositives();
		totalResults += resultSet.getInstances().size();
		if (resultSet.getInstances().size() < 1) {
			System.out.println("0 matches found");
		} else {
			resultSetGenerated = true;
			System.out.println(resultSet.getInstances().size()
					+ " possible matches found");
		}
		positionForShowNext = 0;

	}

	/**
	 * Helper method which prints the next instance in the current result set
	 *
	 */
	private static void handlePrintInstance() {

		Instance instance = null;
		boolean loop = false;

		do {
			loop = false;
			if (positionForShowNext >= resultSet.getInstances().size()) {
				positionForShowNext = LAST_VALID_INDEX;
				break;
			}
			instance = (Instance) resultSet.getInstances().elementAt(
					positionForShowNext);
			if (instance.isFalsePositive()) {
				positionForShowNext++;
				loop = true;
			}
		} while (loop);

		if (instance == null || instance.isFalsePositive()) {
			System.out.println(INAPPROPRIATE_REQUEST);
		} else {
			System.out.println(instance);
			LAST_VALID_INDEX = positionForShowNext;
		}

	}

	/**
	 * Main method runs program. Terminates with a exit code of 0 for success
	 * and -1 for failure.
	 * @param    args    Command line arguments
	 */
	public static void main(String[] args) {

		try {

			/* It is an error to have any command line arguments */
			if (args.length > 0) {
				throw new QUIRKException(QUIRKException.ILL_FORMED_COMMAND);
			}

			/* Pull the index information into a FileIndex object from a
			 * CorpusIndexReader object
			 */
			FileIndex fileIndex = null;
			resultSet = new ResultSet();
			resultSetGenerated = false;
			CorpusIndexReader corpusIndexReader = (new SAMZIndexRWFactory())
					.getReader();

			/* Pull input from the user */
			positionForShowNext = 0;
			fileIndex = corpusIndexReader.readIndex(INDEX);
			in = new BufferedReader(new InputStreamReader(System.in));
			prompt();

			/* Accept input until user enter QUIT command */
			while (!input.equals("quit")) {
				if (input.equals("stats")) {
					STATS_COUNT++;
					printStats(fileIndex);
					prompt();
				} else if (input.equals("index")) {
					INDEX_COUNT++;
					printIndex(fileIndex);
					prompt();
				} else if (input.indexOf("query") == 0) {
					QUERY_COUNT++;
					String rawQuery = input.substring("query".length()).trim();
					handleQueryCase(corpusIndexReader, fileIndex, rawQuery);
					prompt();
				} else if (input.equals("show")) {
					SHOW_COUNT++;
					if (!resultSetGenerated) {
						System.out.println(INAPPROPRIATE_REQUEST);
					} else {
						handlePrintInstance();
					}
					prompt();
				} else if (input.equals("next")) {
					NEXT_COUNT++;
					if (!resultSetGenerated) {
						System.out.println(INAPPROPRIATE_REQUEST);
					} else {
						positionForShowNext++;
						handlePrintInstance();
					}
					prompt();
				} else if (input.indexOf("refine") > -1) {
					REFINE_COUNT++;
					if (!resultSetGenerated) {
						System.out.println(INAPPROPRIATE_REQUEST);
					} else {
						/* Query is refined by 'anding' it with all previous
						 * queries
						 */
						String rawQuery = "";
						String rawQueryRefine = input.substring(
								"refine".length()).trim();
						if ((QueryProcessFacade.Instance().getMostRecentQuery() != null)
								&& (QueryProcessFacade.Instance()
										.getMostRecentQuery().toString()
										.length() > 0)) {
							rawQuery = "("
									+ QueryProcessFacade.Instance()
											.getMostRecentQuery() + ")("
									+ rawQueryRefine + ")";
						} else {
							rawQuery = rawQueryRefine;
						}
						handleQueryCase(corpusIndexReader, fileIndex, rawQuery);
					}
					prompt();
				} else {
					System.out.println(INAPPROPRIATE_REQUEST);
					prompt();
				}
			}
			QUIT_COUNT++;
			
			double sizeRatio = 100 * ((double) new File(INDEX).length() / (double) fileIndex
					.getCorpusTotalChars());

			if (totalResults > 0) {
				double percentFalsePositives = 0;
				percentFalsePositives = ((double) totalFalsePositives / (double) totalResults) * 100;
				System.out.println("False Positive Rate: " + percentFalsePositives + '%');
			} else {
				System.out.println("False Positive Rate: 0%");
			}

			System.out.println("Index to Corpus Ratio: " + sizeRatio + '%');
			System.out.println("Count of STATS: " + STATS_COUNT);
			System.out.println("Count of INDEX: " + INDEX_COUNT);
			System.out.println("Count of QUERY: " + QUERY_COUNT);
			System.out.println("Count of REFINE: " + REFINE_COUNT);
			System.out.println("Count of SHOW: " + SHOW_COUNT);
			System.out.println("Count of NEXT: " + NEXT_COUNT);
			System.out.println("Count of QUIT: " + QUIT_COUNT);

		} catch (AIMException e) {
			System.err.println(e.getMessage());
			System.exit(-1);
		} catch (QUIRKException e) {
			System.err.println(e.getMessage());
			System.exit(-1);
		} catch (Throwable t) {
			System.err.println("ERROR: Program terminated unexpectedly");
			System.exit(-1);
		}
	}

}
